/*
 * User: user
 * Date: 10/12/2007
 * Time: 12:05:41
 */
package org.conann;

import static com.google.common.base.Preconditions.checkState;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import net.jcip.annotations.Immutable;
import org.conann.util.AnnotationUtil;

import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;

@Immutable
public abstract class DynamicBinding<T extends Annotation> implements Annotation {

    @SuppressWarnings({"FieldCanBeLocal"})
    private final Class<T> annotationType;
    private Map<String, Object> parameterValues;

    @SuppressWarnings({"unchecked"})
    protected DynamicBinding() {
        Type[] genericInterfaces = getClass().getGenericInterfaces();
        String genericsErrorMessage = String.format("[%s]: A DynamicBinding annotation implemenatation must be declared generic to a particular annotation type.", getClass());
        Type bindingType = genericInterfaces[0];
        checkState(genericInterfaces.length == 1 && !bindingType.equals(Annotation.class), genericsErrorMessage);
        final Class<?> type = (Class<?>) bindingType;
        checkState(type.isAssignableFrom(getClass()), genericsErrorMessage);
        annotationType = (Class<T>) type;
    }

    @SuppressWarnings({"ControlFlowStatementWithoutBraces"})
    @Override
    public boolean equals(final Object other) {
        if (!(other instanceof Annotation)) {
            return false;
        }
        final Annotation otherAnnotation = (Annotation) other;
        final Class<? extends Annotation> otherAnnotationType = otherAnnotation.annotationType();
        if (!annotationType.equals(otherAnnotationType)) {
            return false;
        }
        Map<String, Object> params = AnnotationUtil.getParameters(this);
        if (params.isEmpty()) {
            return true;
        }
        Map<String, Object> otherParams = AnnotationUtil.getParameters(otherAnnotation);
        for (Map.Entry<String, Object> paramEntry : params.entrySet()) {
            if (!paramEntry.getValue().equals(otherParams.get(paramEntry.getKey()))) {
                return false;
            }
        }
        return true;
    }

    @Override
    public int hashCode() {
        final Class<T> type = annotationType;
        int hashcode = type.hashCode();
        for (Object value : getParameters().values()) {
            if (value != null) {
                hashcode = hashcode + 31 * value.hashCode();
            }
        }
        return hashcode;
    }

    @Override
    public String toString() {
        final StringBuilder sb = new StringBuilder(annotationType.getSimpleName());
        Map<String, Object> parameters = getParameters();
        if (!parameters.isEmpty()) {
            sb.append(' ');
            List<Object> params = Lists.newArrayList();
            for (Object value : parameters.values()) {
                params.add(value);
            }
            sb.append(Iterables.toString(params));
        }
        return sb.toString();
    }

    public Class<? extends Annotation> annotationType() {
        return annotationType;
    }

    private Map<String, Object> getParameters() {
        if (parameterValues == null) {
            parameterValues = AnnotationUtil.getParameters(this);
        }
        return parameterValues;
    }
}